﻿namespace Cellfish.Toolbox.Facts.Tasks
{
    using System;
    using System.Collections.Generic;
    using System.Threading;
    using System.Threading.Tasks;

    using Cellfish.Test;
    using Cellfish.Toolbox.Tasks;

    using Microsoft.VisualStudio.TestTools.UnitTesting;

    [TestClass]
    public class WithProgressExtensionsFacts
    {
        [TestMethod]
        public void Given_null_task_collection_When_adding_progress_Then_ArgumentNullException_is_thrown()
        {
            IEnumerable<Task> tasks1 = null;
            IEnumerable<Task<int>> tasks2 = null;
            Ensure.Throws<ArgumentNullException>(() => tasks1.WithProgress(new Progress<int>()), "Failed for Task and IProgress<int>.");
            Ensure.Throws<ArgumentNullException>(() => tasks1.WithProgress(new Progress<Task>()), "Failed for Task and IProgress<Task>.");
            Ensure.Throws<ArgumentNullException>(() => tasks2.WithProgress(new Progress<int>()), "Failed for Task<T> and IProgress<T>.");
            Ensure.Throws<ArgumentNullException>(() => tasks2.WithProgress(new Progress<Task<int>>()), "Failed for Task<T> and IProgress<Task<T>>.");
        }

        [TestMethod]
        public void Given_task_collection_When_adding_null_progress_Then_ArgumentNullException_is_thrown()
        {
            var tasks1 = new List<Task>();
            var tasks2 = new List<Task<int>>();
            Ensure.Throws<ArgumentNullException>(() => tasks1.WithProgress((IProgress<int>)null), "Failed for Task and IProgress<int>.");
            Ensure.Throws<ArgumentNullException>(() => tasks1.WithProgress((IProgress<Task>)null), "Failed for Task and IProgress<Task>.");
            Ensure.Throws<ArgumentNullException>(() => tasks2.WithProgress((IProgress<int>)null), "Failed for Task<T> and IProgress<T>.");
            Ensure.Throws<ArgumentNullException>(() => tasks2.WithProgress((IProgress<Task<int>>)null), "Failed for Task and IProgress<Task<T>>.");
        }

        [TestMethod]
        public void Given_task_collection_When_adding_counting_progress_Then_progress_reports_completed_tasks()
        {
            var reportedProgress = new List<int>();
            var tasks = new[]
                            {
                                Task.Delay(TimeSpan.Zero), 
                                Task.Factory.StartNew(() => { throw new Exception(); }),
                                Task.Delay(TimeSpan.Zero)
                            };
            var progress = new ProgressSimple<int>(reportedProgress.Add);
            Ensure.TaskFails<Exception>(Task.WhenAll(tasks.WithProgress(progress)), "WhenAll should fail since one task is failing.");
            CollectionAssert.AreEqual(new[] { 1, 2, 3 }, reportedProgress, "Wrong reported sequence. [{0}]", string.Join(", ", reportedProgress));
        }

        [TestMethod]
        public void Given_task_collection_When_adding_progress_Then_progress_reports_completed_tasks()
        {
            int successfulTasks = 0;
            int failedTasks = 0;
            var tasks = new[]
                            {
                                Task.Delay(TimeSpan.Zero), 
                                Task.Factory.StartNew(() => { throw new Exception(); }),
                                Task.Delay(TimeSpan.Zero)
                            };
            var progress = new ProgressSimple<Task>(
                p =>
                {
                    if (p.Status == TaskStatus.RanToCompletion)
                    {
                        successfulTasks++;
                    }
                    else
                    {
                        failedTasks++;
                    }
                });
            Ensure.TaskFails<Exception>(Task.WhenAll(tasks.WithProgress(progress)), "WhenAll should fail since one task is failing.");
            Assert.AreEqual(2, successfulTasks, "Wrong number of successful tasks reported.");
            Assert.AreEqual(1, failedTasks, "Wrong number of failed tasks reported.");
        }

        [TestMethod]
        public void Given_task_collection_with_results_When_adding_result_progress_Then_progress_reports_succesful_tasks()
        {
            var reportedProgress = new List<int>();
            var tasks = new[]
                            {
                                Task.FromResult(42), 
                                Task.Factory.StartNew<int>(() => { throw new Exception(); }),
                                Task.FromResult(4711)
                            };
            var progress = new ProgressSimple<int>(reportedProgress.Add);
            Ensure.TaskFails<Exception>(Task.WhenAll(tasks.WithProgress(progress)), "WhenAll should fail since one task is failing.");
            CollectionAssert.AreEquivalent(new[] { 42, 4711 }, reportedProgress, "Wrong reported results. [{0}]", string.Join(", ", reportedProgress));
        }

        [TestMethod]
        public void Given_task_collection_with_results_When_adding_progress_Then_progress_reports_completed_tasks()
        {
            var successfulTasks = new List<int>();
            int failedTasks = 0;
            var tasks = new[]
                            {
                                Task.FromResult(42), 
                                Task.Factory.StartNew<int>(() => { throw new Exception(); }),
                                Task.FromResult(4711)
                            };
            var progress = new ProgressSimple<Task<int>>(
                p =>
                {
                    if (p.Status == TaskStatus.RanToCompletion)
                    {
                        successfulTasks.Add(p.Result);
                    }
                    else
                    {
                        failedTasks++;
                    }
                });
            Ensure.TaskFails<Exception>(Task.WhenAll(tasks.WithProgress(progress)), "WhenAll should fail since one task is failing.");
            CollectionAssert.AreEquivalent(new[] { 42, 4711 }, successfulTasks, "Wrong reported successes. [{0}]", string.Join(", ", successfulTasks));
            Assert.AreEqual(1, failedTasks, "Wrong number of failed tasks reported.");
        }
    }
}
